#ifndef _DICOM_STORAGE_LOCAL_CACHE_
#define _DICOM_STORAGE_LOCAL_CACHE_

#include <gwbase/base/link_list.h>
#include "dicom/file_object.h"
#include <gwbase/base/conf.h>
#include <gwbase/base/link_list.h>
#include <gwbase/base/logging.h>
#include <gwbase/base/mutex.h>
#include <gwbase/base/util.h>
#include <gwbase/db/sqlite3_wrapper.h>
#include <gwbase/encrypt/digest.h>
#include "dicom/upload/hash_cache.h"

namespace dicom {

struct CacheStat {
  int64_t total_space;
  int64_t used_space;
  int64_t removable_space;
};

struct FileDetail {
  std::string id;
  std::string path;
  std::string src_digest;
  std::string aes_digest;
};

enum FileInTmpDir {
  yes = 1,
  no = 0,
};

enum Sticky {
  sticky = 1,
  removable = 0,
};

class LRUFileCache {
 public:
  LRUFileCache(const std::string& workdir, int64_t capacity);

  virtual ~LRUFileCache();

  struct Handle {};

  // put PATH to local cache storage, return NULL if
  // the cache is full and can not make space because exsiting files
  // in cache are sticky
  virtual Handle* Insert(const std::string& id, const std::string& path,
                         size_t file_size, const std::string& src_digest,
                         const std::string& aes_digest,
                         FileInTmpDir already_in_tmp_dir, Sticky sticky);

  // Must call ReleaseTmpFile() if this funciton return NULL;
  virtual Handle* InsertFromTmpFile(const std::string& id,
                                    const std::string& path,
                                    size_t alloc_tmp_size,
                                    const std::string& src_digest,
                                    const std::string& aes_digest,
                                    Sticky sticky);

  // find a file by id
  virtual Handle* Lookup(const std::string& id);

  // release the handle, if it is the last one reference the file,
  // then the file will be deleted
  virtual void Release(Handle* handle);

  // Now the file is removable
  virtual void MarkAsRemovable(const std::string& id);

  // where the cached file locates
  virtual std::string FilePath(Handle* handle);

  // get file details
  virtual bool GetFileDetail(Handle* handle, FileDetail* detail);

  // remove a file from cache
  virtual void Erase(const std::string& id);

  // alloc/release tmp file
  virtual bool AllocTmpFile(const char* domain, size_t size, std::string* path);
  virtual void ReleaseTmpFile(const char* domain, const std::string& path);

  virtual void GetStat(CacheStat* stat);

  bool InitCache();

 private:
  struct LRUFileHandle : public LinkNode<LRUFileHandle> {
    std::string id;
    std::string path;
    size_t size;
    Sticky sticky;

    std::string src_digest;
    std::string aes_digest;

    int refs;
  };

  void UnrefHandle(LRUFileHandle* h);

  struct FileLogEntry {
    std::string id;
    std::string path;
    size_t file_size;
    int32_t state;
    int32_t sticky;
    Timestamp lru_time;
    std::string src_digest;
    std::string aes_digest;
  };

  // create log table if not exists
  void CreateLogTable();

  // write a log before moving a file to cache, so if
  // crashed when moving, we can delete it when restart
  bool LogStartMoving(const std::string& id, const std::string& path,
                      size_t file_size);

  // moving failed
  bool LogMovingFailed(const std::string& id, const std::string& path,
                       size_t file_size);

  // save a log result
  bool LogMovingResult(const std::string& id, const std::string& path,
                       size_t file_size, const std::string& src_digest,
                       const std::string& aes_digest, Sticky sticky,
                       bool success);

  // helper functions to read/write log
  bool IdExists(const std::string& id);
  bool LoadLogEntry(db::StmtPtr& stmt, FileLogEntry* e);
  bool UpdateLogEntry(const FileLogEntry& e);

  // delete a log, this should be only called _AFTER_ the
  // file is deleted _SUCCESSFULLY_
  bool DeleteFileCacheLog(const std::string& id);

  // when restart cache, we must cleanup stale data:
  // remove all incomplete files
  void RemoveFilesWithIncompleteLog();

  // load cache files from database: only the files with kFileNormal
  // flag will be loaded, others will be deleted in
  // RemoveFilesWithIncompleteLog()
  void LoadCacheFiles();

  // ensure there is at SIZE space to cache new file
  bool MakeSpace(size_t size);

  // try to delete physical file,
  // this function return true if the file is already deleted
  bool DeletePhyFile(const std::string& path);

  void DeleteAllTempFiles();

  Mutex mu_;
  std::string workdir_;
  int64_t used_;
  int64_t capacity_;
  LinkList<LRUFileHandle> files_;
  db::DbPtr db_;
  typedef std::map<std::string, LRUFileHandle*> IdMap;
  IdMap table_;
  std::string tmp_dir_;

  typedef std::map<std::string, size_t> TmpFileMap;
  TmpFileMap tmp_files_;
};

extern LRUFileCache* NewLocalCache(const std::string& workdir, size_t capacity);

extern LRUFileCache::Handle* SaveFileToCacheWithAES(LRUFileCache* cache,
                                                    const std::string& path,
                                                    const std::string& fid,
                                                    std::string hex_key,
                                                    HashEntry* hash);
};

#endif  // _DICOM_STORAGE_LOCAL_CACHE_
